﻿using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using JetBrains.ReSharper.Daemon.CSharp.Stages;
using JetBrains.ReSharper.Daemon.Stages;
using JetBrains.ReSharper.Daemon.Stages.Dispatcher;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.Util;
using JetBrains.Util.Special;
using MugenMvvmToolkit.ReSharper.Highlighting;
using MugenMvvmToolkit.ReSharper.Infrastructure;

namespace MugenMvvmToolkit.ReSharper.ViewModels.Analyzers
{
    [ElementProblemAnalyzer(new[] { typeof(ICSharpFunctionDeclaration) },
        HighlightingTypes = new[] { typeof(TaskBusyHandlerHighlighting) })]
    public class TaskBusyHandlerAnalyzer : ElementProblemAnalyzerBase<ICSharpFunctionDeclaration>
    {
        #region Overrides of ElementProblemAnalyzerBase<IInvocationExpression>

        protected override void Run(ICSharpFunctionDeclaration element, ElementProblemAnalyzerData data,
            IHighlightingConsumer consumer)
        {
            if (!TypeProvider.Task.IsResolved || element.Body == null)
                return;
            var @class = element.FindParent<IClassLikeDeclaration>();
            if (@class == null || !@class.IsOfType(TypeProvider.ViewModelInterface))
                return;
            var expressions = new Dictionary<IInvocationExpression, List<IInvocationExpression>>();
            var allVisitedMethods = new HashSet<IInvocationExpression>();

            element.Body
                .ProcessDescendantsForResolve(new OneActionProcessor(node =>
                {
                    var invocationExpression = node as IInvocationExpression;
                    if (invocationExpression == null || allVisitedMethods.Contains(invocationExpression))
                        return;
                    var visitedItems = new List<IInvocationExpression>();
                    var root = GetInvocationRoot(invocationExpression, visitedItems);
                    allVisitedMethods.AddRange(visitedItems);
                    if (root == null)
                        return;
                    List<IInvocationExpression> callChain;
                    if (!expressions.TryGetValue(root, out callChain))
                        expressions[root] = visitedItems;
                    else
                    {
                        foreach (var visitedItem in visitedItems)
                        {
                            if (callChain.Contains(visitedItem))
                                continue;
                            callChain.Add(visitedItem);
                        }
                    }
                }, node => true));

            foreach (var keyValuePair in expressions.Where(pair => !HasTaskBusyHandler(pair.Value, pair.Key)))
            {
                consumer.AddHighlighting(new TaskBusyHandlerHighlighting(keyValuePair.Value, keyValuePair.Key));
            }
        }

        #endregion

        #region Methods

        [CanBeNull]
        private IInvocationExpression GetInvocationRoot(IInvocationExpression element, List<IInvocationExpression> expressions)
        {
            while (true)
            {
                var resolve = element.GetReferenceDeclaredElement<IParametersOwner>();
                if (resolve == null || !resolve.IsReturnTypeTask(TypeProvider))
                    return expressions.FirstOrDefault();

                var expression = element.InvokedExpression as IReferenceExpression;
                if (expression == null)
                    return expressions.FirstOrDefault();

                if (!expressions.Contains(element))
                    expressions.Insert(0, element);
                if (expression.QualifierExpression == null)
                    return element;

                var invocationExpression = expression.QualifierExpression as IInvocationExpression;
                if (invocationExpression == null)
                {
                    var referenceExpression = expression.QualifierExpression as IReferenceExpression;
                    var variableDeclaration = referenceExpression.GetDeclaredElement<IDeclaredElement>() as IInitializerOwnerDeclaration;
                    if (variableDeclaration == null || variableDeclaration.Initializer == null)
                        return element;
                    invocationExpression = (variableDeclaration.Initializer as IExpressionInitializer).IfNotNull(initializer => initializer.Value) as IInvocationExpression;
                    if (invocationExpression == null)
                        return null;
                }
                element = invocationExpression;
            }
        }

        private bool HasTaskBusyHandler(IEnumerable<IInvocationExpression> expressions, IInvocationExpression rootExpression)
        {
            if (rootExpression.ExtensionQualifier != null)
            {
                var expression = rootExpression.InvokedExpression as IReferenceExpression;
                var attributesSet = expression.GetQualifierExpressionDeclaredElement<IDeclaredElement>() as IAttributesSet;
                if (ToolkitExtensions.HasTaskBusyHandler(attributesSet))
                    return true;
            }
            return expressions.Any(expression => expression.FindParent<IReturnStatement>() != null || ToolkitExtensions.HasTaskBusyHandler(expression, TypeProvider));
        }

        #endregion
    }
}